
#include "../iface/APRInterface.h"
#include "../iface/debug.h"
#include "../utils/StringUtils.h"
#include "APRFile.h"

/** Converts file endings on a given file from any or mixed type to
 * Unix 0x0A endings. Reads blocks of text, converts them to 0x0A and
 * writes them back to the same file. Ensure that only text files are
 * run through this, as the process corrupts binary data.
 * 
 * @param fname Filename to convert (path can be expandable).
 * 
 * @param pool Memory pool to use. */
#define LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH 1024
void APRFile::lineEndingsToUnix(const char *fname, apr_pool_t *pool)
{
   apr_status_t status;
   apr_file_t *filep;
   // Expand the tilde in the path to the home directory if present
   const char *mp_expandpath = expandPath(fname, pool);
   // Open the file
   status = apr_file_open(&filep, mp_expandpath, APR_FOPEN_BINARY|APR_FOPEN_READ|APR_FOPEN_WRITE, APR_FPROT_OS_DEFAULT, pool);
   CheckAPRError(status);
   // Check if an error occurred, if it did abandon the file open procedure.
   if (status != APR_SUCCESS) { errprint("could not convert line endings for file '%s'", mp_expandpath); return; }
   // Set up block lengths
   int readblocklength = LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH;
   char *readblock = (char *)malloc(sizeof(char)*LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH);
   char *writeblock = (char *)malloc(sizeof(char)*LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH);
   // While not the end of the file.
   apr_size_t totalwrote = 0;
   apr_size_t readwriteoffset = 0;
   while (readblocklength == LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH)
   {
      // Read the next block
      apr_size_t bytesread = LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH;
      status = apr_file_read_full(filep, readblock, LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH, &bytesread);
      if (bytesread == 0) break;
      if (!APR_STATUS_IS_EOF(status))
      {
         CheckAPRError(status);
         if (status != APR_SUCCESS)
         {
            errprint("could not read %" APR_SIZE_T_FMT " instead read %" APR_SIZE_T_FMT " from file '%s'", (apr_size_t)LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH, bytesread, mp_expandpath);
            break;
         }
      }
      int i, j;
      // Go through the block, converting line endings in-place
      for (i = 0, j = 0; i < (((int)(bytesread)) - 1); i++, j++)
      {
         // Check for Mac line endings (though this is for old Mac versions).
         if ((readblock[i] == 0x0D) && (readblock[i+1] != 0x0A)) readblock[i] == 0x0A;
         writeblock[j] = readblock[i];
         // Check for Windows line endings. If the line ending is
         // split, then nothing will be written as the j does not
         // include the last byte and the next call will get the Unix
         // (correct) line ending in the next block.
         if ((readblock[i] == 0x0D) && (readblock[i+1] == 0x0A)) j--;
      }
      if (bytesread < LINE_ENDINGS_TO_UNIX_READ_BLOCK_LENGTH)
      {
         writeblock[j] = readblock[i];
         j++;
      }
      // Seek backwards to rewrite the last block with the modified
      // line endings
      apr_off_t offset = -(bytesread + readwriteoffset);
      status = apr_file_seek(filep, APR_CUR, &offset);
      CheckAPRError(status);
      if (status != APR_SUCCESS)
      {
         errprint("could not seek backwards (a seek backward of %" APR_OFF_T_FMT " bytes happened instead of %" APR_OFF_T_FMT ") in file '%s'",
            -offset, (apr_off_t)(-bytesread), mp_expandpath);
         break;
      }
      // Write the j finished bytes
      apr_size_t byteswritten = j;
      status = apr_file_write_full(filep, writeblock, j, &byteswritten);
      CheckAPRError(status);
      if (status != APR_SUCCESS)
      {
         errprint("could not write %" APR_SIZE_T_FMT " instead wrote %" APR_SIZE_T_FMT " to file '%s'", (apr_size_t)j, byteswritten, mp_expandpath);
         break;
      }
      // Apply the seek offset between the read and write parts
      totalwrote += byteswritten;
      readwriteoffset += ((bytesread - j) - 1);
      // Set the read block length and use this to find the end of the
      // file
      readblocklength = (int)(bytesread);
      // Seek to the next block to process
      apr_off_t offset2 = readwriteoffset;
      status = apr_file_seek(filep, APR_CUR, &offset2);
      CheckAPRError(status);
      if (status != APR_SUCCESS)
      {
         errprint("could not seek forwards (a seek forward of %" APR_OFF_T_FMT " bytes happened instead of %" APR_OFF_T_FMT ") in file '%s'",
            offset2, (apr_off_t)(bytesread - j), mp_expandpath);
         break;
      }
   }
   free(readblock);
   free(writeblock);
   apr_file_trunc(filep, totalwrote);
   apr_file_close(filep);
}

/** Makes recursive directories along the (expandable) path given.
 * This is a thin wrapper around apr_dir_make_recursive.
 * 
 * @param dname The path (which can be expandable) along which to
 * create the recursive structures.
 * 
 * @param perm The default permissions for the directories that are to
 * be created.
 * 
 * @param pool The memory pool to be allocated from for memory
 * required for these.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::dirMakeRecursiveWrapper(const char *dname, apr_fileperms_t perm, apr_pool_t *pool)
{
   return apr_dir_make_recursive(expandPath(dname, pool), perm, pool);
}

/** Opens a file using an expandable path wrapper around
 * apr_file_open.
 *
 * @param[out] newf The resulting file pointer is written here.
 * 
 * @param fname The path (which can be expandable) to the file to be
 * opened.
 * 
 * @param flag The flags for the mode in which to open the file.
 * 
 * @param perm The default permissions for the file, if created.
 * 
 * @param pool The memory pool to be allocated from for memory
 * required for these operations.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::openWrapper(apr_file_t **newf, const char *fname,
   apr_int32_t flag, apr_fileperms_t perm, apr_pool_t *pool)
{
   return apr_file_open(newf, expandPath(fname, pool), flag, perm, pool);
}

/** Deletes a file using an expandable path wrapper around
 * apr_file_remove.
 *
 * @param path The path (which can be expandable) to the file to be
 * deleted.
 * 
 * @param cont The memory pool to be allocated from for memory
 * required for these operations.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::removeWrapper(const char *path, apr_pool_t *cont)
{
   return apr_file_remove(expandPath(path, cont), cont);
}

/** Renames a file using an expandable path wrapper around
 * apr_file_rename.
 *
 * @param from_path The path (which can be expandable) to the file to
 * be renamed.
 * 
 * @param to_path The path (which can be expandable) to the file in
 * its final form.
 * 
 * @param pool The memory pool to be allocated from for memory
 * required for these operations.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::renameWrapper(const char *from_path, const char *to_path, apr_pool_t *pool)
{
   return apr_file_rename(expandPath(from_path, pool), expandPath(to_path, pool), pool);
}

/** Copy a file using an expandable path wrapper around apr_file_copy.
 *
 * @param from_path The path (which can be expandable) to the file to
 * be renamed.
 * 
 * @param to_path The path (which can be expandable) to the file in
 * its final form.
 * 
 * @param perms The default permissions for the file, if created.
 * 
 * @param pool The memory pool to be allocated from for memory
 * required for these operations.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::copyWrapper(const char *from_path, const char *to_path, apr_fileperms_t perms, apr_pool_t *pool)
{
   return apr_file_copy(expandPath(from_path, pool), expandPath(to_path, pool), perms, pool);
}

/** Append a file to a destination file using an expandable path
 * wrapper around apr_file_append.
 *
 * @param from_path The path (which can be expandable) to the file to
 * be read.
 * 
 * @param to_path The path (which can be expandable) to the file to
 * which from_path is to be appended.
 * 
 * @param perms The default permissions for the file, if created.
 * 
 * @param pool The memory pool to be allocated from for memory
 * required for these operations.
 * 
 * @return The APR status return code for the function. */
apr_status_t APRFile::appendWrapper(const char *from_path, const char *to_path, apr_fileperms_t perms, apr_pool_t *pool)
{
   return apr_file_append(expandPath(from_path, pool), expandPath(to_path, pool), perms, pool);
}

/** Expands paths. Expands the first tilde to my home directory if it
 * exists.
 * 
 * @param unexpandpath The unexpanded input path.
 * 
 * @param mp The memory pool from which to take memory to seamlessly
 * wrap the output string.
 * 
 * @return A string with the first tilde (if it exists, replaced by
 * the home directory of the current user). */
const char *APRFile::expandPath(const char *unexpandpath, apr_pool_t *mp)
{
   if (unexpandpath[0] != '~') return unexpandpath;
   apr_status_t status;
   char *basic = (char *)"/tmp";
#ifdef APR_HAS_USER
   apr_uid_t uid;
   apr_gid_t gid;
   status = apr_uid_current(&uid, &gid, mp);
   CheckAPRError(status);
   if (status != 0)
   {
      errprint("could not get user id");
      return NULL;
   }
   char *username = NULL;
   status = apr_uid_name_get(&username, uid, mp);
   CheckAPRError(status);
   if (status != 0)
   {
      errprint("could not get user name");
      return NULL;
   }
   char *dirname = NULL;
   status = apr_uid_homepath_get(&dirname, username, mp);
   CheckAPRError(status);
   if (status != 0)
   {
      errprint("could not get home directory");
      return NULL;
   }
   basic = dirname;
#endif
   return StringUtils::mpprintf(mp, "%s%s", basic, &(unexpandpath[1]));
}

/** Expands paths. Expands the first tilde to my home directory if it
 * exists. This uses a memory pool with a very short life, and so
 * should probably be avoided for performance. This returns a string
 * freeable by malloc (and the callers responsibility), and so does
 * not require memory pools in or out of the function.
 * 
 * @param unexpandpath The input path with the tilde (maybe).
 * 
 * @return A string with the first tilde (if it exists, replaced by
 * the home directory of the current user). */
char *APRFile::expandPathToNewString(const char *unexpandpath)
{
   apr_status_t status;
   apr_pool_t *mp;
   status = apr_pool_create(&mp, NULL);
   CheckAPRError(status);
   char *retval = strdup(expandPath(unexpandpath, mp));
   apr_pool_destroy(mp);
   return retval;
}

/** Blocks out a given amount of space for data to be written into later.
 * Enables early detection of disk full errors and helps stop people writing
 * more than they should.
 * 
 * @param filename The file name of the file to block out (with 0xFF).
 * 
 * @param length The length of data to block out (with 0xFF).
 * 
 * @param mustcancel A mechanism to detect if roll-back is required. When
 * writing data in blocks, the whole operation must be atomically
 * roll-back-able, which is what this helps to provide.
 * 
 * @param mp The memory pool for allocating file writing structures.
 * 
 * @return Zero on success, one if the block is too big, two if the data
 * cannot be written. */
int APRFile::blockOutFileSpace(const char *filename, apr_size_t length, int *mustcancel, apr_pool_t *mp)
{
   const int64_t MAX_BLOCK_SIZE = 1048576*64;
   if (length > MAX_BLOCK_SIZE/*64Mb*/)
   {
      errprint("block too big (size '%" APR_SIZE_T_FMT "' but maximum is set to '%" APR_INT64_T_FMT "')", length, MAX_BLOCK_SIZE);
      *mustcancel = 1;
      return 1;
   }
   apr_file_t *newFP;
   apr_status_t status;
   char *fakebuffer = (char *)malloc(sizeof(char)*length);
   for (int i = 0; i < (int)(length); i++)
      fakebuffer[i] = (char)(0xFF);
   status = openWrapper(&newFP, (const char *)filename, APR_FOPEN_CREATE|APR_FOPEN_WRITE|APR_FOPEN_BUFFERED|APR_FOPEN_BINARY, APR_OS_DEFAULT, mp);
   CheckAPRError(status);
   apr_size_t newsize = length;
   status = apr_file_write_full(newFP, fakebuffer, length, &newsize);
   CheckAPRError(status);
   status = apr_file_close(newFP);
   CheckAPRError(status);
   if (length != newsize)
   {
      errprint("could not write data (bytes written %" APR_SIZE_T_FMT " out of %" APR_SIZE_T_FMT ")", newsize, length);
      *mustcancel = 1;
      return 2;
   }
   free(fakebuffer);
   return 0;
}

/** Get the size of the specified file. Uses either a stat call,
 * or seeks to the end of the file and finds the offset (more reliable
 * most of the time).
 * 
 * @param filename The name of the file to be sized.
 * 
 * @param mp The memory pool used to allocate file structures.
 * 
 * @return The size of the file. */
apr_size_t APRFile::getFileSize(const char *filename, apr_pool_t *mp)
{
   apr_file_t *newFP;
   apr_status_t status;
   status = openWrapper(&newFP, filename, APR_FOPEN_READ|APR_FOPEN_BUFFERED|APR_FOPEN_BINARY, APR_OS_DEFAULT, mp);
   if (status != APR_SUCCESS)
   {
      return 0;
   }
   apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
   status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
   CheckAPRError(status);
#else
   apr_off_t offset = 0;
   status = apr_file_seek(newFP, APR_END, &offset);
   CheckAPRError(status);
   finfo.size = offset;
   offset = 0;
   status = apr_file_seek(newFP, APR_SET, &offset);
   CheckAPRError(status);
#endif
   status = apr_file_close(newFP);
   CheckAPRError(status);
   return (apr_size_t)finfo.size;
}
